﻿using System;
using System.ComponentModel.Design;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using EnvDTE;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Task = System.Threading.Tasks.Task;

namespace Agile.VSIX {
    /// <summary>
    /// 排序
    /// </summary>
    internal sealed class SortLinesCommand {
        /// <summary>
        /// Command ID.
        /// </summary>
        public const int CommandId = 4133;

        /// <summary>
        /// Command menu group (command set GUID).
        /// </summary>
        public static readonly Guid CommandSet = new Guid("1c21120b-12b7-4d3a-ab61-1962f6426c0c");

        /// <summary>
        /// VS Package that provides this command, not null.
        /// </summary>
        private readonly AsyncPackage package;

        /// <summary>
        /// Initializes a new instance of the <see cref="SortLinesCommand"/> class.
        /// Adds our command handlers for menu (commands must exist in the command table file)
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        /// <param name="commandService">Command service to add command to, not null.</param>
        private SortLinesCommand(AsyncPackage package,OleMenuCommandService commandService) {
            this.package = package ?? throw new ArgumentNullException(nameof(package));
            commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));

            var menuCommandID = new CommandID(CommandSet,CommandId);
            var menuItem = new MenuCommand(this.Execute,menuCommandID);
            commandService.AddCommand(menuItem);
        }

        /// <summary>
        /// Gets the instance of the command.
        /// </summary>
        public static SortLinesCommand Instance {
            get;
            private set;
        }

        /// <summary>
        /// Gets the service provider from the owner package.
        /// </summary>
        private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider {
            get {
                return this.package;
            }
        }

        /// <summary>
        /// Initializes the singleton instance of the command.
        /// </summary>
        /// <param name="package">Owner package, not null.</param>
        public static async Task InitializeAsync(AsyncPackage package) {
            // Switch to the main thread - the call to AddCommand in SortLinesCommand's constructor requires
            // the UI thread.
            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);

            OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
            Instance = new SortLinesCommand(package,commandService);
        }

        /// <summary>
        /// This function is the callback used to execute the command when the menu item is clicked.
        /// See the constructor to see how the menu item is associated with this function using
        /// OleMenuCommandService service and MenuCommand class.
        /// </summary>
        /// <param name="sender">Event sender.</param>
        /// <param name="e">Event args.</param>
        private void Execute(object sender,EventArgs e) {
            ThreadHelper.ThrowIfNotOnUIThread();
            try {
                DTE dte = ServiceProvider.GetServiceAsync(typeof(DTE)).Result as DTE;
                if(dte.ActiveDocument != null) {
                    var selection = (TextSelection)dte.ActiveDocument.Selection;
                    if(selection != null) {
                        SortText(selection);
                        OutputWindowHelper.DiagnosticWriteLine("" + dte.ActiveDocument.Name + "(" + dte.ActiveDocument.Type + ")文档排序成功。");
                    } else {
                        OutputWindowHelper.WarningWriteLine("请选择活动文档！");
                    }
                } else {
                    OutputWindowHelper.WarningWriteLine("没有打开文档！");
                }
            } catch(Exception ex) {
                OutputWindowHelper.ExceptionWriteLine(string.Format(CultureInfo.CurrentCulture,ex.Message),ex);
            }
        }
        private void SortText(TextSelection textSelection) {
            // If the selection has no length, try to pick up the next line.
            if(textSelection.IsEmpty) {
                textSelection.LineDown(true);
                textSelection.EndOfLine(true);
            }
            // Start of selection should always be at the beginning of the line.
            var start = textSelection.TopPoint.CreateEditPoint();
            start.StartOfLine();
            // End of selection should always be at the start of the following line (i.e. extend past the last line's newline character).
            var end = textSelection.BottomPoint.CreateEditPoint();
            if(!end.AtStartOfLine) {
                end.EndOfLine();
                end.CharRight();
            }
            // Capture the selected text.
            var selectedText = start.GetText(end);
            // Create the sorted text lines.
            var splitText = selectedText.Split(new[] { Environment.NewLine },StringSplitOptions.RemoveEmptyEntries);
            var orderedText = splitText.OrderBy(x => x);
            var sb = new StringBuilder();
            foreach(var line in orderedText) {
                sb.AppendLine(line);
            }
            var sortedText = sb.ToString();
            // If the selected and sorted text do not match, delete and insert the replacement.
            if(!selectedText.Equals(sortedText,StringComparison.CurrentCulture)) {
                start.Delete(end);
                var insertCursor = start.CreateEditPoint();
                insertCursor.Insert(sortedText);
                textSelection.MoveToPoint(start,false);
                textSelection.MoveToPoint(insertCursor,true);
            }
        }
    }
}
